package com.rbac.configuration.secutity;


import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.rbac.entity.vo.UserPrincipal;
import com.rbac.exception.SecurityException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 认证服务,动态路由认证,验证请求是否存在以及权限是否匹配
 *
 * @author zhangliang
 * @version 1.0
 * @date 2021/1/25 15:36
 */
@Component
public class RBACAuthorityService {

    private final RequestMappingHandlerMapping mapping;

    protected static final Logger logger = LoggerFactory.getLogger(RBACAuthorityService.class);

    public RBACAuthorityService(RequestMappingHandlerMapping mapping) {
        this.mapping = mapping;
    }

    /**
     * 发送请求验证是否有权限
     */
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        //通过RequestMappingHandlerMapping校验请求是否存在
        logger.debug("检查请求是否存在:{}", checkRequest(request));

        Object userInfo = authentication.getPrincipal();
        boolean hasPermission = false;

        if (userInfo instanceof UserDetails) {
            UserPrincipal principal = (UserPrincipal) userInfo;
            Long userId = principal.getId();

            //这里可以做动态拦截，我这先不做
            return true;
        }

        return false;
    }

    /**
     * 校验请求是否存在
     *
     * @param request 请求
     */
    private boolean checkRequest(HttpServletRequest request) {
        String currentMethod = request.getMethod();
        logger.info(request.getServletPath());

        Multimap<String, String> urlMapping = allUrlMapping();

        for (String uri : urlMapping.keySet()) {
            // 通过 AntPathRequestMatcher 匹配 url
            // 可以通过 2 种方式创建 AntPathRequestMatcher
            // 1：new AntPathRequestMatcher(uri,method) 这种方式可以直接判断方法是否匹配，因为这里我们把 方法不匹配 自定义抛出，所以，我们使用第2种方式创建
            // 2：new AntPathRequestMatcher(uri) 这种方式不校验请求方法，只校验请求路径
            AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(uri);
            if (antPathMatcher.matches(request)) {
                if (!urlMapping.get(uri).contains(currentMethod)) {
                    throw new SecurityException(HttpStatus.METHOD_NOT_ALLOWED);
                } else {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获取 所有URL Mapping，返回格式为{"/test":["GET","POST"],"/sys":["GET","DELETE"]}
     *
     * @return {@link ArrayListMultimap} 格式的 URL Mapping
     */
    private Multimap<String, String> allUrlMapping() {
        Multimap<String, String> urlMapping = ArrayListMultimap.create();

        // 获取url与类和方法的对应信息
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = mapping.getHandlerMethods();

        handlerMethodMap.forEach((k, v) -> {
            // 获取当前 key 下的获取所有URL
            assert k.getPatternsCondition() != null;
            Set<PathPattern> url = Objects.requireNonNull(k.getPathPatternsCondition()).getPatterns();
            RequestMethodsRequestCondition method = k.getMethodsCondition();


            url.forEach(s -> urlMapping.putAll(s.getPatternString(), method.getMethods().stream().map(Enum::toString).collect(Collectors.toList())));
        });

        return urlMapping;
    }
}
